import os
import re
import queue
import time
import threading
import webbrowser
import random
import requests
import tkinter as tk
from tkinter import messagebox, filedialog, ttk
from tkinterdnd2 import TkinterDnD, DND_FILES
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
import shutil
import subprocess

# Пути к файлам
TOKENS_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\токены для автопостинга.txt"
FAILED_TOKENS_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\Токены неуспешных публикаций.txt"
LAST_POST_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\last_post.txt"
POSTS_DIR = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\посты"
UPLOADED_POSTS_DIR = os.path.join(POSTS_DIR, "загруженные")
AUTOPOST_STATE_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\autopost_state.txt"
POST_LIMIT_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\post_limit_state.txt"
PHOTO_COUNT_FILE_PATH = r"C:\Users\1\Desktop\VS 2022\Автопостинг ВК\photo_count_state.txt"

# Словарь для хранения информации о пользователях
user_info_dict = {}

# --- Цвета темной темы ---
DARK_BACKGROUND = "#2b2b2b"
DARK_FOREGROUND = "#dcdcdc"
DARK_WIDGET_BG = "#3c3f41"
DARK_WIDGET_FG = "#bbbbbb"
DARK_INSERT_BG = "#dcdcdc"
DARK_BUTTON_BG = "#4a4d4f"
DARK_BUTTON_FG = "#dcdcdc"
DARK_BUTTON_ACTIVE_BG = "#5a5d5f"
DARK_BUTTON_PRESSED_BG = "#6a6d6f"
DARK_DISABLED_FG = "#777777"
DARK_DISABLED_BG = "#3c3f41"
DARK_ACCENT_SUCCESS_BG = "#274e13"
DARK_ACCENT_FAILED_BG = "#592323"
DARK_SUCCESS_TEXT_COLOR = "#8fbc8f"
DARK_FAILED_TEXT_COLOR = "#f08080"
DARK_LINK_COLOR = "#6897bb"
DARK_PROGRESS_BAR = "#6897bb"
DARK_TROUGH_COLOR = "#313335"
DARK_HEADER_BG = "#3c3f41"
DARK_HEADER_FG = "#dcdcdc"
DARK_SELECT_BG = "#54585a"
DARK_SELECT_FG = "#ffffff"
FONT_STYLE = ("Arial", 11)
HEADER_FONT = ("Arial", 12, "bold")

# Очередь для сообщений из потоков
log_queue = queue.Queue()

# Флаг для остановки процесса публикации
stop_flag = threading.Event()

# Счётчик для уникальных тегов гиперссылок
hyperlink_counter = 0

# --- Переменные для статуса Wi-Fi, IP и локации ---
current_wifi_status_var = None
current_ip_var = None
current_location_var = None

# --- Наборы токенов для копирования из UI ---
final_failed_tokens_for_ui_copy = set()
final_successful_tokens_for_ui_copy = set()


# Функция для сортировки с учётом чисел в названиях файлов
def natural_key(filename):
    basename = os.path.basename(filename)
    return [text for text in re.split(r'(\d+)', basename)]

# --- Функция для получения SSID Wi-Fi ---
def get_current_wifi_ssid():
    """Возвращает SSID текущей Wi-Fi сети (для Windows) или сообщение об ошибке."""
    try:
        process = subprocess.Popen(['netsh', 'wlan', 'show', 'interfaces'],
                                     stdout=subprocess.PIPE,
                                     text=True,
                                     encoding='utf-8',
                                     errors='ignore',
                                     creationflags=subprocess.CREATE_NO_WINDOW)
        stdout, stderr = process.communicate(timeout=5)
        if process.returncode == 0:
            for line in stdout.splitlines():
                if "SSID" in line and "BSSID" not in line:
                    match = re.search(r":\s*(.+)", line)
                    if match:
                        ssid = match.group(1).strip()
                        if ssid: return ssid
            return "Нет подключения"
        else:
            return "Ошибка опроса"
    except FileNotFoundError:
        return "netsh не найден"
    except subprocess.TimeoutExpired:
        return "Тайм-аут опроса"
    except Exception:
        return "Ошибка Wi-Fi"

# --- Функция для получения внешнего IP-адреса ---
def get_external_ip():
    """Получает внешний IP-адрес через API ipify."""
    try:
        response = requests.get('https://api.ipify.org', timeout=5)
        response.raise_for_status()
        return response.text.strip()
    except requests.RequestException:
        return "Недоступно"

# --- Функция для получения локации по IP ---
def get_location_by_ip(ip):
    """Получает приблизительную локацию по IP через API ipinfo.io."""
    try:
        response = requests.get(f'https://ipinfo.io/{ip}/json', timeout=5)
        response.raise_for_status()
        data = response.json()
        city = data.get('city', 'Неизвестно')
        region = data.get('region', 'Неизвестно')
        country = data.get('country', 'Неизвестно')
        return f"{city}, {region}, {country}"
    except requests.RequestException:
        return "Недоступно"

# --- Функция обновления меток Wi-Fi, IP и локации ---
def update_info_display():
    """Обновляет метки Wi-Fi, IP и локации каждые 15 секунд."""
    if root and current_wifi_status_var and current_ip_var and current_location_var:
        ssid = get_current_wifi_ssid()
        current_wifi_status_var.set(f"Wi-Fi: {ssid}")
        
        ip = get_external_ip()
        current_ip_var.set(f"IP: {ip}")
        
        if ip != "Недоступно":
            location = get_location_by_ip(ip)
            current_location_var.set(f"Локация: {location}")
        else:
            current_location_var.set("Локация: Недоступно")
    
    root.after(15000, update_info_display)  # Обновление каждые 15 секунд

# --- Функции для работы с ВКонтакте ---
def get_user_info(token):
    url = 'https://api.vk.com/method/users.get'
    params = {'access_token': token, 'v': '5.131'}
    response = requests.get(url, params=params)
    data = response.json()
    if 'response' in data:
        user_info = data['response'][0]
        user_info_dict[token] = f"{user_info['first_name']} {user_info['last_name']}"
        return user_info['id'], user_info_dict[token]
    elif 'error' in data and data['error'].get('error_code') == 5:
        raise Exception(f"Ошибка авторизации: {data['error']['error_msg']}")
    else:
        raise Exception("Ошибка получения информации о пользователе: " + str(data.get('error', {})))

def upload_photo(token, photo_path):
    url = 'https://api.vk.com/method/photos.getWallUploadServer'
    params = {'access_token': token, 'v': '5.131'}
    response = requests.post(url, params=params)
    response.raise_for_status()
    upload_info = response.json()
    if 'response' not in upload_info:
        raise Exception("Ошибка получения сервера для загрузки фото: " + str(upload_info.get('error', {})))
    upload_url = upload_info['response']['upload_url']
    with open(photo_path, 'rb') as photo_file:
        files = {'photo': photo_file}
        upload_response = requests.post(upload_url, files=files)
        upload_response.raise_for_status()
        upload_data = upload_response.json()
    if 'photo' not in upload_data:
        raise Exception("Ошибка загрузки фото: " + str(upload_data))
    return upload_data

def save_photo(token, upload_data, user_id):
    url = 'https://api.vk.com/method/photos.saveWallPhoto'
    params = {
        'access_token': token,
        'user_id': user_id,
        'server': upload_data['server'],
        'photo': upload_data['photo'],
        'hash': upload_data['hash'],
        'v': '5.131'
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    data = response.json()
    if 'response' not in data:
        raise Exception("Ошибка сохранения фото: " + str(data.get('error', {})))
    return data

def upload_video(token, video_path):
    url = 'https://api.vk.com/method/video.save'
    params = {'access_token': token, 'v': '5.131', 'name': os.path.basename(video_path)}
    response = requests.post(url, params=params)
    response.raise_for_status()
    video_info = response.json()
    if 'response' not in video_info:
        raise Exception("Ошибка получения сервера для загрузки видео: " + str(video_info.get('error', {})))
    upload_url = video_info['response']['upload_url']
    with open(video_path, 'rb') as video_file:
        files = {'video_file': video_file}
        upload_response = requests.post(upload_url, files=files) # Загружаем файл
        upload_response.raise_for_status() # Проверяем статус загрузки файла
    if 'video_id' not in video_info['response']:
        raise Exception("Ошибка загрузки или сохранения видео: " + str(video_info.get('error', video_info)))
    return video_info['response']


def post_to_vk(token, user_id, message, attachments):
    url = 'https://api.vk.com/method/wall.post'
    params = {
        'access_token': token,
        'owner_id': user_id,
        'message': message,
        'attachments': attachments,
        'v': '5.131'
    }
    response = requests.post(url, params=params)
    response.raise_for_status()
    data = response.json()
    if 'error' in data:
        raise Exception("Ошибка публикации поста: " + data['error']['error_msg'])
    return data

# --- Логика публикации ---
def submit_post():
    stop_flag.clear()
    post_button['state'] = tk.DISABLED
    stop_button['state'] = tk.NORMAL
    if autopost_var.get():
        try:
            post_limit = int(post_limit_var.get())
            photo_count = int(photo_count_var.get())
            if post_limit <= 0 or photo_count <= 0:
                messagebox.showerror("Ошибка", "Лимит постов и количество фотографий должны быть больше 0.")
                post_button['state'] = tk.NORMAL
                stop_button['state'] = tk.DISABLED
                return
            thread = threading.Thread(target=post_to_accounts_concurrently, args=(post_limit, photo_count), daemon=True)
        except ValueError:
            messagebox.showerror("Ошибка", "Введите корректные числовые значения для лимита постов и количества фотографий.")
            post_button['state'] = tk.NORMAL
            stop_button['state'] = tk.DISABLED
            return
    else:
        thread = threading.Thread(target=post_to_accounts_concurrently, daemon=True)
    thread.start()

def stop_posting():
    stop_flag.set()
    log_queue.put("Публикация остановлена пользователем.\n")
    stop_button['state'] = tk.DISABLED

def update_add_schedule_button_state(*args):
    add_schedule_button['state'] = tk.DISABLED if autopost_var.get() else tk.NORMAL
    post_limit_entry['state'] = tk.NORMAL if autopost_var.get() else tk.DISABLED
    photo_count_entry['state'] = tk.NORMAL if autopost_var.get() else tk.DISABLED


def post_to_account(token, user_id_param, message, photos, videos):
    if stop_flag.is_set():
        return ("Публикация остановлена пользователем.", False, None)
    max_attempts = 5
    attempt = 0
    user_name = ""
    actual_user_id = user_id_param

    while attempt < max_attempts:
        if stop_flag.is_set():
            return ("Публикация остановлена пользователем.", False, None)
        attempt += 1
        try:
            if actual_user_id is None or attempt == 1:
                actual_user_id, user_name = get_user_info(token)
            elif not user_name:
                 _, user_name = get_user_info(token)

            log_queue.put(f"Публикация на аккаунте: {user_name if user_name else 'ID ' + str(actual_user_id)}... Попытка {attempt}\n")
            attachments = []
            for photo_idx, photo in enumerate(photos):
                if stop_flag.is_set(): return ("Публикация остановлена.", False, None)
                log_queue.put(f"Загрузка фото {photo_idx+1}/{len(photos)}: {os.path.basename(photo)} на {user_name}...\n")
                upload_data = upload_photo(token, photo)
                saved_photo = save_photo(token, upload_data, actual_user_id)
                if 'response' in saved_photo and saved_photo['response']:
                    attachments.append(f"photo{saved_photo['response'][0]['owner_id']}_{saved_photo['response'][0]['id']}")
                else:
                    raise Exception(f"Не удалось сохранить фото {os.path.basename(photo)}. Ответ: {saved_photo}")

            for video_idx, video in enumerate(videos):
                if stop_flag.is_set(): return ("Публикация остановлена.", False, None)
                log_queue.put(f"Загрузка видео {video_idx+1}/{len(videos)}: {os.path.basename(video)} на {user_name}...\n")
                video_upload_response = upload_video(token, video) 
                video_owner_id = video_upload_response.get('owner_id', actual_user_id) 
                attachments.append(f"video{video_owner_id}_{video_upload_response['video_id']}")

            result = post_to_vk(token, actual_user_id, message, ','.join(attachments))
            post_id = result['response']['post_id']
            link = f"https://vk.com/wall{actual_user_id}_{post_id}"
            return (f"Пост успешно опубликован на аккаунте: {user_name}", link, actual_user_id)

        except Exception as e:
            error_text = str(e)
            if attempt >= max_attempts:
                user_name_display = user_name if user_name else user_info_dict.get(token, token)
                error_message = f"Ошибка на аккаунте: {user_name_display}: {error_text} после {max_attempts} попыток\n"
                return error_message, False, token 
            else:
                user_name_display = user_name if user_name else user_info_dict.get(token, token)
                log_queue.put(f"Попытка {attempt} не удалась на аккаунте {user_name_display}. Ошибка: {error_text}\n")
                if "too many requests per second" in error_text.lower() or \
                   (isinstance(e, requests.exceptions.HTTPError) and e.response.status_code == 429) or \
                   ('error_code' in error_text and '6' in error_text): 
                    log_queue.put("Обнаружена ошибка 'too many requests'. Увеличиваю задержку...\n")
                    time.sleep(random.uniform(7,15)) 
                else:
                    time.sleep(random.uniform(2,5)) 
    return ("Не удалось опубликовать после максимального количества попыток.", False, token)


def move_uploaded_files(files):
    os.makedirs(UPLOADED_POSTS_DIR, exist_ok=True)
    for file_path in files:
        try:
            if os.path.exists(file_path):
                shutil.move(file_path, os.path.join(UPLOADED_POSTS_DIR, os.path.basename(file_path)))
                log_queue.put(f"Файл перемещён: {os.path.basename(file_path)}\n")
            else:
                log_queue.put(f"Файл для перемещения не найден: {os.path.basename(file_path)}\n")
        except Exception as e:
            log_queue.put(f"Ошибка при перемещении файла {os.path.basename(file_path)}: {e}\n")

def post_to_accounts_concurrently(post_limit=None, photo_count=None):
    failed_tokens_set = set() 
    local_successful_tokens_in_current_run = set()
    accounts_posted_set = set()

    if autopost_var.get() and post_limit and photo_count:
        if not os.path.exists(POSTS_DIR):
            log_queue.put(f"Папка {POSTS_DIR} не найдена.\n")
            root.after(0, lambda: post_button.config(state=tk.NORMAL))
            root.after(0, lambda: stop_button.config(state=tk.DISABLED))
            root.after(0, update_add_schedule_button_state)
            return

        all_photos = sorted([
            os.path.join(POSTS_DIR, f) for f in os.listdir(POSTS_DIR)
            if f.lower().endswith(('.jpg', '.jpeg', '.png')) 
        ], key=natural_key)

        if not all_photos:
            log_queue.put("В папке 'посты' нет фотографий для автопостинга.\n")
            root.after(0, lambda: post_button.config(state=tk.NORMAL))
            root.after(0, lambda: stop_button.config(state=tk.DISABLED))
            root.after(0, update_add_schedule_button_state)
            return

        posting_schedule.clear() 
        for i in range(0, min(post_limit * photo_count, len(all_photos)), photo_count):
            photos_for_post = all_photos[i:i + photo_count]
            posting_schedule.append({'text': '', 'photos': photos_for_post, 'videos': []})
        log_queue.put(f"Сформировано {len(posting_schedule)} постов для автопубликации.\n")
        root.after(0, update_posting_schedule_table)


    if not tokens:
        log_queue.put("Нет доступных токенов для публикации.\n")
        root.after(0, lambda: post_button.config(state=tk.NORMAL))
        root.after(0, lambda: stop_button.config(state=tk.DISABLED))
        root.after(0, update_add_schedule_button_state)
        return

    if not posting_schedule:
        log_queue.put("Нет наборов публикаций для выполнения.\n")
        root.after(0, lambda: post_button.config(state=tk.NORMAL))
        root.after(0, lambda: stop_button.config(state=tk.DISABLED))
        root.after(0, update_add_schedule_button_state)
        return

    total_posts_to_make = len(posting_schedule) * len(tokens) 
    root.after(0, lambda: progress_bar.config(maximum=total_posts_to_make, value=0))

    for widget in [output_text, successful_text, failed_text]: 
        root.after(0, lambda w=widget: w.config(state=tk.NORMAL))
        root.after(0, lambda w=widget: w.delete("1.0", tk.END))
    root.after(0, lambda: output_text.config(state=tk.DISABLED))

    successful_posts_count = 0
    failed_posts_count = 0 

    for entry in posting_schedule:
        entry['photos'] = sorted(entry.get('photos', []), key=natural_key)
        entry['videos'] = sorted(entry.get('videos', []), key=natural_key)
    log_queue.put("Файлы в наборах публикаций отсортированы.\n")


    for entry_idx, entry in enumerate(posting_schedule):
        if stop_flag.is_set(): break

        message = entry['text']
        photos_to_post = entry['photos']
        videos_to_post = entry['videos']
        successful_tokens_for_this_entry = 0 

        valid_tokens_for_this_entry = [token for token in tokens if token not in failed_tokens_set]

        if not valid_tokens_for_this_entry:
            log_queue.put(f"Для публикации №{entry_idx + 1} нет доступных (не сфейлившихся ранее) токенов.\n")
            continue 

        log_queue.put(f"--- Начало публикации набора №{entry_idx + 1} (Текст: '{message[:30].replace(os.linesep, ' ')}...', Фото: {len(photos_to_post)}, Видео: {len(videos_to_post)}) ---\n")

        with ThreadPoolExecutor(max_workers=10) as executor:
            future_to_token = {
                executor.submit(post_to_account, token, None, message, photos_to_post, videos_to_post): token
                for token in valid_tokens_for_this_entry
            }

            for future in as_completed(future_to_token):
                if stop_flag.is_set(): 
                    for f_in_loop in future_to_token: 
                        if not f_in_loop.done(): f_in_loop.cancel()
                    break 

                token_from_future = future_to_token[future]
                try:
                    result = future.result() 
                    if len(result) == 3:
                        status_message, outcome, problematic_item = result
                        if outcome == False : 
                            failed_posts_count += 1
                            update_failed_posts_threadsafe(status_message)
                            if problematic_item: 
                                failed_tokens_set.add(problematic_item) 
                        else: 
                            successful_posts_count += 1
                            successful_tokens_for_this_entry +=1
                            local_successful_tokens_in_current_run.add(token_from_future)
                            if problematic_item: 
                                accounts_posted_set.add(problematic_item) 
                            update_successful_posts_threadsafe(status_message, outcome) 
                    else: 
                        failed_posts_count += 1
                        update_failed_posts_threadsafe(f"Неизвестная ошибка обработки для токена: {token_display_name(token_from_future)}.\n")
                        failed_tokens_set.add(token_from_future)

                except Exception as exc: 
                    failed_posts_count += 1
                    error_msg = f"Критическая ошибка для {token_display_name(token_from_future)} при получении результата: {exc}\n"
                    update_failed_posts_threadsafe(error_msg)
                    failed_tokens_set.add(token_from_future) 

                current_progress = successful_posts_count + failed_posts_count
                root.after(0, lambda pb=progress_bar, val=current_progress: pb.config(value=val))
                update_progress_label_threadsafe(successful_posts_count, failed_posts_count, total_posts_to_make)
        
        if not stop_flag.is_set():
            if autopost_var.get():
                if photos_to_post or videos_to_post:
                    log_msg_prefix = ""
                    if not valid_tokens_for_this_entry:
                         log_msg_prefix = f"Набор №{entry_idx + 1}: не было валидных токенов для публикации."
                    elif successful_tokens_for_this_entry == len(valid_tokens_for_this_entry):
                        log_msg_prefix = f"Набор №{entry_idx + 1} успешно опубликован на всех ({successful_tokens_for_this_entry}) аккаунтах."
                    elif successful_tokens_for_this_entry > 0:
                        log_msg_prefix = f"Набор №{entry_idx + 1} опубликован на {successful_tokens_for_this_entry} из {len(valid_tokens_for_this_entry)} аккаунтов."
                    else: 
                        log_msg_prefix = f"Набор №{entry_idx + 1} не опубликован ни на одном из {len(valid_tokens_for_this_entry)} аккаунтов."
                    
                    log_queue.put(f"{log_msg_prefix} Перемещение файлов...\n")
                    move_uploaded_files(photos_to_post + videos_to_post)
                elif not (photos_to_post or videos_to_post): 
                    if not valid_tokens_for_this_entry:
                        log_queue.put(f"Набор №{entry_idx + 1} (текстовый): не было валидных токенов для публикации.\n")
                    elif successful_tokens_for_this_entry == len(valid_tokens_for_this_entry) and len(valid_tokens_for_this_entry) > 0:
                        log_queue.put(f"Набор №{entry_idx + 1} (текстовый) успешно опубликован на всех ({len(valid_tokens_for_this_entry)}) доступных аккаунтах.\n")
                    elif len(valid_tokens_for_this_entry) > 0:
                        log_queue.put(f"Набор №{entry_idx + 1} (текстовый) опубликован не на всех ({successful_tokens_for_this_entry}/{len(valid_tokens_for_this_entry)}) аккаунтах.\n")
        else: 
            if autopost_var.get() and (photos_to_post or videos_to_post):
                log_queue.put(f"Обработка набора №{entry_idx + 1} была прервана пользователем. Файлы не будут перемещены.\n")

        if stop_flag.is_set(): break
    
    global final_failed_tokens_for_ui_copy, final_successful_tokens_for_ui_copy
    final_failed_tokens_for_ui_copy = failed_tokens_set.copy() 
    final_successful_tokens_for_ui_copy = local_successful_tokens_in_current_run.copy()

    if failed_tokens_set: 
        write_failed_tokens(failed_tokens_set)

    final_message = "Публикация завершена.\n" if not stop_flag.is_set() else "Процесс был остановлен пользователем.\n"
    log_queue.put(final_message)

    final_progress = successful_posts_count + failed_posts_count
    root.after(0, lambda: progress_bar.config(value=final_progress))
    update_progress_label_threadsafe(successful_posts_count, failed_posts_count, total_posts_to_make) 

    root.after(0, lambda: stop_button.config(state=tk.DISABLED))
    root.after(0, lambda: post_button.config(state=tk.NORMAL))
    root.after(0, update_add_schedule_button_state)


def token_display_name(token_value):
    return user_info_dict.get(token_value, token_value)


def write_failed_tokens(failed_tokens_set_param):
    separator = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + " ------------------------\n"
    try:
        with open(FAILED_TOKENS_FILE_PATH, 'a', encoding='utf-8') as file:
            file.write(separator)
            for token in failed_tokens_set_param:
                file.write(token + '\n')
        log_queue.put(f"Токены неуспешных публикаций записаны в {FAILED_TOKENS_FILE_PATH}\n")
    except Exception as e:
        log_queue.put(f"Ошибка записи токенов неуспешных публикаций: {e}\n")

# --- Измененная функция для копирования токенов ---
def copy_tokens_to_clipboard(token_set, type_name):
    if not token_set:
        log_queue.put(f"Нет {type_name} токенов для копирования в буфер.\n") # Запись в лог
        return
    try:
        tokens_str = "\n".join(sorted(list(token_set)))
        root.clipboard_clear()
        root.clipboard_append(tokens_str)
        log_queue.put(f"{type_name.capitalize()} токены ({len(token_set)} шт.) скопированы в буфер обмена.\n") # Запись в лог
        # messagebox.showinfo убран
    except Exception as e:
        log_queue.put(f"Ошибка копирования токенов в буфер: {e}\n")
        messagebox.showerror("Ошибка", f"Не удалось скопировать токены: {e}", parent=root) # Окно с ошибкой остается


def update_progress_label_threadsafe(success_count, failed_count, total_expected_posts):
    processed_posts = success_count + failed_count
    remaining_overall = total_expected_posts - processed_posts

    progress_info = {
        'success_count': success_count,
        'failed_count': failed_count,
        'remaining_count': max(0, remaining_overall)
    }
    log_queue.put(('update_progress', progress_info))


def update_successful_posts_threadsafe(message, link):
    log_queue.put(('update_success', message, link))

def update_failed_posts_threadsafe(message):
    log_queue.put(('update_failed', message))

def process_queue():
    try:
        while True:
            item = log_queue.get_nowait()
            if isinstance(item, str): 
                output_text.config(state=tk.NORMAL)
                output_text.insert(tk.END, item)
                if auto_scroll_var.get():
                    output_text.see(tk.END)
                output_text.config(state=tk.DISABLED)
            elif isinstance(item, tuple): 
                if item[0] == 'update_progress':
                    info = item[1]
                    progress_label.config(text=f"Осталось попыток постов (из общего плана): {info['remaining_count']}")
                    successful_posts_label.config(text=f"Успешные публикации: {info['success_count']}")
                    failed_posts_label.config(text=f"Неуспешные публикации: {info['failed_count']}")
                elif item[0] == 'update_success':
                    message, link = item[1], item[2]
                    update_successful_posts(message, link)
                elif item[0] == 'update_failed':
                    message = item[1]
                    update_failed_posts(message) 
    except queue.Empty:
        pass
    root.after(100, process_queue)


def update_successful_posts(message, link):
    global hyperlink_counter
    tag_name = f"hyperlink_{hyperlink_counter}"
    hyperlink_counter += 1

    successful_text.config(state=tk.NORMAL)
    successful_text.insert(tk.END, message + " ")
    successful_text.insert(tk.END, link, tag_name) 
    successful_text.tag_config(tag_name, foreground=DARK_LINK_COLOR, underline=True)
    successful_text.tag_bind(tag_name, "<Button-1>", lambda e, l=link: webbrowser.open(l))
    successful_text.tag_bind(tag_name, "<Enter>", lambda e: successful_text.config(cursor="hand2"))
    successful_text.tag_bind(tag_name, "<Leave>", lambda e: successful_text.config(cursor="arrow"))
    successful_text.insert(tk.END, "\n")
    if auto_scroll_var.get(): 
        successful_text.see(tk.END)


def update_failed_posts(message):
    failed_text.config(state=tk.NORMAL)
    failed_text.insert(tk.END, message) 
    if auto_scroll_var.get(): 
        failed_text.see(tk.END)
    failed_text.config(state=tk.DISABLED) 


def load_tokens_ui_update():
    global tokens
    if os.path.exists(TOKENS_FILE_PATH):
        try:
            with open(TOKENS_FILE_PATH, 'r', encoding='utf-8') as file:
                tokens = [token.strip() for token in file.readlines() if token.strip()]
            tokens_label.config(text=f"Токенов загружено: {len(tokens)}")
            if tokens:
                enable_buttons() 
                autopost_check['state'] = tk.NORMAL 
                log_queue.put(f"Загружено {len(tokens)} токенов.\n")
            else:
                log_queue.put("Файл токенов пуст.\n")
                autopost_check['state'] = tk.DISABLED 
                autopost_var.set(False) 
        except Exception as e:
            log_queue.put(f"Ошибка чтения файла токенов: {e}\n")
            messagebox.showerror("Ошибка токенов", f"Не удалось прочитать файл токенов: {e}")
            tokens = []
            tokens_label.config(text="Токенов: 0 (ошибка)")
            autopost_check['state'] = tk.DISABLED
            autopost_var.set(False)
    else:
        log_queue.put(f"Файл с токенами не найден: {TOKENS_FILE_PATH}\n")
        tokens_label.config(text="Токенов: 0 (файл не найден)")
        autopost_check['state'] = tk.DISABLED
        autopost_var.set(False)
    update_add_schedule_button_state()

def enable_buttons():
    post_button['state'] = tk.NORMAL
    remove_schedule_button['state'] = tk.NORMAL

def create_context_menu(widget):
    context_menu = tk.Menu(widget, tearoff=0,
                           bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG,
                           activebackground=DARK_SELECT_BG, activeforeground=DARK_SELECT_FG,
                           disabledforeground=DARK_DISABLED_FG, font=FONT_STYLE)
    try:
        can_cut = bool(widget.selection_get()) if hasattr(widget, 'selection_present') and widget.selection_present() else False
        can_copy = bool(widget.selection_get()) if hasattr(widget, 'selection_present') and widget.selection_present() else False
        
        context_menu.add_command(label="Вырезать", command=lambda: widget.event_generate("<<Cut>>"),
                                 state=tk.NORMAL if can_cut else tk.DISABLED)
        context_menu.add_command(label="Копировать", command=lambda: widget.event_generate("<<Copy>>"),
                                 state=tk.NORMAL if can_copy else tk.DISABLED)
        try:
            if hasattr(widget, 'clipboard_get'): widget.clipboard_get() 
            paste_state = tk.NORMAL
        except tk.TclError:
            paste_state = tk.DISABLED
        context_menu.add_command(label="Вставить", command=lambda: widget.event_generate("<<Paste>>"),
                                 state=paste_state)
        context_menu.add_separator(background=DARK_BACKGROUND)
        if hasattr(widget, 'event_generate'): # Убедимся, что виджет поддерживает <<SelectAll>>
            context_menu.add_command(label="Выбрать все", command=lambda: widget.event_generate("<<SelectAll>>"))
    except (tk.TclError, AttributeError): 
        pass 

    if widget in [output_text, successful_text, failed_text]:
        auto_scroll_label = "Отключить автопрокрутку" if auto_scroll_var.get() else "Включить автопрокрутку"
        context_menu.add_separator(background=DARK_BACKGROUND)
        context_menu.add_command(label=auto_scroll_label, command=lambda: auto_scroll_var.set(not auto_scroll_var.get()))
    return context_menu

def show_context_menu(event, widget):
    context_menu = create_context_menu(widget)
    try:
        if hasattr(widget, 'selection_present') and widget.selection_present():
             can_cut_copy = tk.NORMAL
        else:
             can_cut_copy = tk.DISABLED
        
        try: context_menu.entryconfig("Вырезать", state=can_cut_copy)
        except tk.TclError: pass
        try: context_menu.entryconfig("Копировать", state=can_cut_copy)
        except tk.TclError: pass

    except (tk.TclError, AttributeError) : pass 

    context_menu.tk_popup(event.x_root, event.y_root)


def add_posting_set():
    add_window = tk.Toplevel(root)
    add_window.title("Добавить набор публикации")
    add_window.geometry("700x550") 
    add_window.configure(bg=DARK_BACKGROUND)
    add_window.attributes("-topmost", True) 

    for i in range(6): add_window.grid_rowconfigure(i, weight=0) 
    add_window.grid_rowconfigure(2, weight=1) 
    add_window.grid_rowconfigure(4, weight=1) 
    add_window.grid_columnconfigure(0, weight=1) 
    add_window.grid_columnconfigure(1, weight=0) 
    add_window.grid_columnconfigure(2, weight=0) 

    title_label = ttk.Label(add_window, text="Новый набор публикации", font=HEADER_FONT, background=DARK_BACKGROUND, foreground=DARK_FOREGROUND)
    title_label.grid(row=0, column=0, columnspan=3, pady=(10, 5), padx=10, sticky="w")

    files_label_text = ttk.Label(add_window, text="Файлы (Фото и Видео):", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND)
    files_label_text.grid(row=1, column=0, padx=10, pady=5, sticky="w")

    files_listbox_frame = ttk.Frame(add_window) 
    files_listbox_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
    files_listbox_frame.grid_rowconfigure(0, weight=1)
    files_listbox_frame.grid_columnconfigure(0, weight=1)

    files_listbox = tk.Listbox(files_listbox_frame, selectmode=tk.MULTIPLE, width=40, height=10, font=FONT_STYLE,
                                bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG,
                                selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                                borderwidth=1, relief="sunken", exportselection=False)
    files_listbox.grid(row=0, column=0, sticky="nsew")

    files_listbox_scroll = ttk.Scrollbar(files_listbox_frame, orient="vertical", command=files_listbox.yview)
    files_listbox_scroll.grid(row=0, column=1, sticky="nsw")
    files_listbox.config(yscrollcommand=files_listbox_scroll.set)

    files_buttons_frame = ttk.Frame(add_window, style="Dark.TFrame") 
    files_buttons_frame.grid(row=2, column=1, padx=5, pady=5, sticky="nw") 

    posting_schedule_files = [] 

    def add_files_drop_event(event, file_list_ref, listbox_ref): 
        try:
            dropped_files_str = event.data
            if dropped_files_str.startswith('{') and dropped_files_str.endswith('}'):
                processed_path = dropped_files_str[1:-1]
                if '} {' in processed_path:
                     files_from_dnd = [p.strip() for p in re.split(r'(?<=}) (?={)', dropped_files_str) if p.strip()]
                     files_from_dnd = [f[1:-1] if (f.startswith('{') and f.endswith('}')) else f for f in files_from_dnd]
                else:
                    files_from_dnd = [processed_path] 
            else:
                files_from_dnd = add_window.tk.splitlist(dropped_files_str)
        except tk.TclError: 
            files_from_dnd = [event.data] 

        valid_files_to_add = [f for f in files_from_dnd
                              if os.path.exists(f) and f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif', '.mp4', '.avi', '.mov', '.mkv'))]
        newly_added_basenames_dnd = []
        for file_path_dnd in valid_files_to_add:
            if file_path_dnd not in file_list_ref: 
                file_list_ref.append(file_path_dnd)
                listbox_ref.insert(tk.END, os.path.basename(file_path_dnd)) 
                newly_added_basenames_dnd.append(os.path.basename(file_path_dnd))
        if newly_added_basenames_dnd:
            log_queue.put(f"Добавлено файлов через DnD: {newly_added_basenames_dnd}\n")


    files_listbox.drop_target_register(DND_FILES)
    files_listbox.dnd_bind('<<Drop>>', lambda event: add_files_drop_event(event, posting_schedule_files, files_listbox))


    def add_files_dialog():
        selected_files = filedialog.askopenfilenames(
            title="Выберите фото и/или видео",
            filetypes=[("Image and Video files", "*.jpg;*.jpeg;*.png;*.gif;*.mp4;*.avi;*.mov;*.mkv"), ("All files", "*.*")],
            parent=add_window 
        )
        newly_added_basenames = []
        for file in selected_files:
            if file not in posting_schedule_files:
                posting_schedule_files.append(file)
                files_listbox.insert(tk.END, os.path.basename(file))
                newly_added_basenames.append(os.path.basename(file))
        if newly_added_basenames:
            log_queue.put(f"Добавлено файлов через диалог: {newly_added_basenames}\n")


    add_files_button = ttk.Button(files_buttons_frame, text="+", width=3, command=add_files_dialog, style="Dark.TButton")
    add_files_button.pack(side=tk.TOP, pady=(0,2)) 

    def remove_selected_files_dialog():
        selected_indices = list(files_listbox.curselection())
        selected_indices.sort(reverse=True) 
        removed_basenames = []
        for index in selected_indices:
            removed_basenames.append(os.path.basename(posting_schedule_files[index]))
            files_listbox.delete(index)
            del posting_schedule_files[index]
        if removed_basenames:
            log_queue.put(f"Удалены выбранные файлы: {removed_basenames}\n")

    remove_files_button = ttk.Button(files_buttons_frame, text="-", width=3, command=remove_selected_files_dialog, style="Dark.TButton")
    remove_files_button.pack(side=tk.TOP, pady=(2,0))

    text_label = ttk.Label(add_window, text="Текст сообщения:", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND)
    text_label.grid(row=3, column=0, padx=10, pady=(10, 5), sticky="w")

    message_entry_frame = ttk.Frame(add_window)
    message_entry_frame.grid(row=4, column=0, columnspan=2, padx=10, pady=5, sticky="nsew") 
    message_entry_frame.grid_rowconfigure(0, weight=1)
    message_entry_frame.grid_columnconfigure(0, weight=1)

    message_entry = tk.Text(message_entry_frame, height=5, width=50, font=FONT_STYLE, wrap="word",
                            bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG, insertbackground=DARK_INSERT_BG,
                            selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                            borderwidth=1, relief="sunken")
    message_entry.grid(row=0, column=0, sticky="nsew")
    message_entry.bind("<Button-3>", lambda event: show_context_menu(event, message_entry))


    message_scrollbar = ttk.Scrollbar(message_entry_frame, orient="vertical", command=message_entry.yview)
    message_scrollbar.grid(row=0, column=1, sticky='nsw')
    message_entry.config(yscrollcommand=message_scrollbar.set)

    def paste_message_in_child(entry_widget):
        try:
            clipboard_text = add_window.clipboard_get() 
            entry_widget.insert(tk.INSERT, clipboard_text)
        except tk.TclError: 
            messagebox.showerror("Ошибка", "Буфер обмена пуст или содержит неподдерживаемые данные.", parent=add_window)


    paste_button_child = ttk.Button(add_window, text="Вставить", style="Dark.TButton",
                                    command=lambda: paste_message_in_child(message_entry))
    paste_button_child.grid(row=4, column=2, padx=5, pady=(5,0), sticky="nw")


    buttons_frame = ttk.Frame(add_window, style="Dark.TFrame")
    buttons_frame.grid(row=5, column=0, columnspan=3, pady=10, sticky="ew")
    buttons_frame.grid_columnconfigure(0, weight=1) 
    buttons_frame.grid_columnconfigure(1, weight=1) 
    buttons_frame.grid_columnconfigure(2, weight=1) 

    inner_buttons_frame = ttk.Frame(buttons_frame, style="Dark.TFrame") 
    inner_buttons_frame.pack() 

    def save_entry_dialog(close_after=False):
        text = message_entry.get("1.0", tk.END).strip()
        if not posting_schedule_files: 
            messagebox.showwarning("Предупреждение", "Добавьте хотя бы один файл.", parent=add_window)
            return False 

        if not text and not messagebox.askyesno("Подтверждение", "Сообщение пустое. Продолжить?", parent=add_window):
            return False 

        photos_paths = [f for f in posting_schedule_files if f.lower().endswith(('.jpg', '.jpeg', '.png', '.gif'))]
        videos_paths = [f for f in posting_schedule_files if f.lower().endswith(('.mp4', '.avi', '.mov', '.mkv'))]

        posting_schedule.append({'text': text, 'photos': photos_paths, 'videos': videos_paths})
        update_posting_schedule_table() 

        message_entry.delete("1.0", tk.END)
        files_listbox.delete(0, tk.END)
        posting_schedule_files.clear() 

        log_queue.put("Новый набор публикации сохранен.\n")

        if close_after:
            add_window.destroy()
        return True 


    save_and_new_button = ttk.Button(inner_buttons_frame, text="Сохранить и Новый", style="Dark.TButton",
                                     command=lambda: save_entry_dialog(close_after=False))
    save_and_new_button.pack(side=tk.LEFT, padx=5)

    save_button = ttk.Button(inner_buttons_frame, text="Сохранить и Закрыть", style="Dark.TButton",
                             command=lambda: save_entry_dialog(close_after=True))
    save_button.pack(side=tk.LEFT, padx=5)

    cancel_button = ttk.Button(inner_buttons_frame, text="Отмена", style="Dark.TButton", command=add_window.destroy)
    cancel_button.pack(side=tk.LEFT, padx=5)
    
    add_window.focus_set() 
    add_window.grab_set()  


def remove_posting_set():
    selected_items_iids = posting_schedule_table.selection() 
    if not selected_items_iids:
        messagebox.showwarning("Предупреждение", "Выберите запись для удаления.")
        return

    try:
        indices_to_remove = sorted([int(iid_str) for iid_str in selected_items_iids], reverse=True)
    except ValueError:
        messagebox.showerror("Ошибка", "Неверный формат IID в таблице.")
        return

    removed_count = 0
    for index in indices_to_remove:
        if 0 <= index < len(posting_schedule): 
            del posting_schedule[index]
            log_queue.put(f"Удалена публикация (бывший №{index + 1} в списке до удаления).\n")
            removed_count +=1
        else:
            log_queue.put(f"Ошибка: попытка удаления публикации с индексом {index}, который вне диапазона.\n")

    if removed_count > 0:
        update_posting_schedule_table() 
    else:
        log_queue.put("Не было удалено ни одной публикации (возможно, из-за неверных индексов).\n")



def update_posting_schedule_table():
    posting_schedule_table.delete(*posting_schedule_table.get_children())
    for idx, entry in enumerate(posting_schedule):
        photos_count = len(entry.get('photos', []))
        videos_count = len(entry.get('videos', []))
        photos_str = f"{photos_count} фото"
        videos_str = f"{videos_count} видео"
        text_preview = (entry['text'][:30].replace(os.linesep, ' ') + '...') if len(entry['text']) > 30 else entry['text'].replace(os.linesep, ' ')
        posting_schedule_table.insert("", "end", iid=str(idx), values=(idx + 1, photos_str, videos_str, text_preview))


def show_table_context_menu(event, table):
    context_menu = tk.Menu(table, tearoff=0, font=FONT_STYLE,
                           bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG,
                           activebackground=DARK_SELECT_BG, activeforeground=DARK_SELECT_FG,
                           disabledforeground=DARK_DISABLED_FG)
    context_menu.add_command(label="Выбрать все", command=lambda t=table: select_all_table(t))
    if table.selection():
        context_menu.add_command(label="Удалить выбранные", command=remove_posting_set)
    else:
        context_menu.add_command(label="Удалить выбранные", command=remove_posting_set, state=tk.DISABLED)

    context_menu.tk_popup(event.x_root, event.y_root)

def select_all_table(table):
    table.selection_set(table.get_children()) 
    return 'break' 

def load_last_post():
    if os.path.exists(LAST_POST_FILE_PATH):
        try:
            with open(LAST_POST_FILE_PATH, 'r', encoding='utf-8') as file:
                value = file.read().strip()
                last_post_var.set(value)
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке последнего выложенного поста: {e}\n")
    else:
        last_post_var.set("") 

def save_last_post(*args):
    value = last_post_var.get()
    try:
        with open(LAST_POST_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(value)
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении последнего выложенного поста: {e}\n")

def save_autopost_state(*args):
    try:
        with open(AUTOPOST_STATE_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(str(autopost_var.get())) 
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении состояния автопубликации: {e}\n")

def load_autopost_state():
    if os.path.exists(AUTOPOST_STATE_FILE_PATH):
        try:
            with open(AUTOPOST_STATE_FILE_PATH, 'r', encoding='utf-8') as file:
                state_str = file.read().strip()
                autopost_var.set(state_str.lower() == 'true') 
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке состояния автопубликации: {e}\n")
            autopost_var.set(False) 
    else:
        autopost_var.set(False) 

def save_post_limit_settings(*args):
    try:
        with open(POST_LIMIT_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(post_limit_var.get())
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении лимита постов: {e}\n")

def save_photo_count_settings(*args):
    try:
        with open(PHOTO_COUNT_FILE_PATH, 'w', encoding='utf-8') as file:
            file.write(photo_count_var.get())
    except Exception as e:
        log_queue.put(f"Ошибка при сохранении количества фотографий: {e}\n")

def load_post_and_photo_settings():
    if os.path.exists(POST_LIMIT_FILE_PATH):
        try:
            with open(POST_LIMIT_FILE_PATH, 'r', encoding='utf-8') as file:
                post_limit_var.set(file.read().strip())
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке лимита постов: {e}\n")
            post_limit_var.set("1") 
    else:
        post_limit_var.set("1")

    if os.path.exists(PHOTO_COUNT_FILE_PATH):
        try:
            with open(PHOTO_COUNT_FILE_PATH, 'r', encoding='utf-8') as file:
                photo_count_var.set(file.read().strip())
        except Exception as e:
            log_queue.put(f"Ошибка при загрузке количества фотографий: {e}\n")
            photo_count_var.set("1") 
    else:
        photo_count_var.set("1")


tokens = []
posting_schedule = [] 

# --- GUI Setup ---
root = TkinterDnD.Tk() 
root.title("Автопостинг ВКонтакте")
root.configure(bg=DARK_BACKGROUND)

current_wifi_status_var = tk.StringVar(value="Wi-Fi: Сканирование...")
current_ip_var = tk.StringVar(value="IP: Сканирование...")
current_location_var = tk.StringVar(value="Локация: Сканирование...")


style = ttk.Style()
try:
    style.theme_use("clam") 
except tk.TclError:
    style.theme_use("default") 

style.configure(".", font=FONT_STYLE, background=DARK_BACKGROUND, foreground=DARK_FOREGROUND)
style.configure("TFrame", background=DARK_BACKGROUND)
style.configure("Dark.TFrame", background=DARK_BACKGROUND) 
style.configure("TLabel", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND, font=FONT_STYLE)
style.configure("Header.TLabel", font=HEADER_FONT) 

style.configure("TButton", foreground=DARK_BUTTON_FG, background=DARK_BUTTON_BG, font=FONT_STYLE, padding=(10, 5), relief="raised", borderwidth=1)
style.map("TButton",
          foreground=[("disabled", DARK_DISABLED_FG),
                      ("active", DARK_BUTTON_FG), 
                      ("pressed", DARK_BUTTON_FG)], 
          background=[("disabled", DARK_DISABLED_BG),
                      ("active", DARK_BUTTON_ACTIVE_BG), 
                      ("pressed", DARK_BUTTON_PRESSED_BG)], 
          relief=[("pressed", "sunken"), ("!pressed", "raised")])

style.configure("Dark.TButton", foreground=DARK_BUTTON_FG, background=DARK_BUTTON_BG, font=FONT_STYLE, padding=(8,4), relief="raised", borderwidth=1)
style.map("Dark.TButton",
    foreground=[("disabled", DARK_DISABLED_FG), ("active", DARK_BUTTON_FG)],
    background=[("disabled", DARK_DISABLED_BG), ("active", DARK_BUTTON_ACTIVE_BG)],
    relief=[("pressed", "sunken"), ("!pressed", "raised")])


style.configure("TLabelFrame", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND, font=FONT_STYLE, borderwidth=1, relief="groove")
style.configure("TLabelFrame.Label", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND, font=HEADER_FONT) 

style.configure("TCheckbutton", background=DARK_BACKGROUND, foreground=DARK_FOREGROUND,
                indicatorbackground=DARK_WIDGET_BG, indicatorforeground=DARK_PROGRESS_BAR, 
                indicatormargin=3, indicatorrelief="flat", indicatoron=True, font=FONT_STYLE)
style.map("TCheckbutton",
    foreground=[("disabled", DARK_DISABLED_FG), ("active", DARK_FOREGROUND)], 
    background=[("active", DARK_BACKGROUND)], 
    indicatorbackground=[("selected", DARK_WIDGET_BG), ("pressed", DARK_WIDGET_BG)],
    indicatorcolor=[("selected", DARK_PROGRESS_BAR), ("pressed", DARK_PROGRESS_BAR)] 
)


style.configure("TEntry", fieldbackground=DARK_WIDGET_BG, foreground=DARK_WIDGET_FG, insertcolor=DARK_INSERT_BG, font=FONT_STYLE,
                borderwidth=1, relief="sunken") 
style.map("TEntry",
    foreground=[("disabled", DARK_DISABLED_FG)],
    fieldbackground=[("disabled", DARK_DISABLED_BG)])


style.configure("Horizontal.TProgressbar", troughcolor=DARK_TROUGH_COLOR, background=DARK_PROGRESS_BAR, thickness=15)

style.configure("Vertical.TScrollbar", troughcolor=DARK_TROUGH_COLOR, background=DARK_WIDGET_BG, arrowcolor=DARK_FOREGROUND,
                borderwidth=0, relief="flat")
style.map("Vertical.TScrollbar",
    background=[("active", DARK_BUTTON_ACTIVE_BG)], 
    arrowcolor=[("pressed", DARK_LINK_COLOR), ("active", DARK_LINK_COLOR)] 
)

style.configure("Treeview", background=DARK_WIDGET_BG, fieldbackground=DARK_WIDGET_BG, foreground=DARK_WIDGET_FG, font=FONT_STYLE,
                rowheight=int(FONT_STYLE[1] * 2.2)) 
style.map("Treeview",
          background=[("selected", DARK_SELECT_BG)],
          foreground=[("selected", DARK_SELECT_FG)])
style.configure("Treeview.Heading", background=DARK_HEADER_BG, foreground=DARK_HEADER_FG, font=HEADER_FONT, relief="raised", padding=(5,3))
style.map("Treeview.Heading",
    background=[("active", DARK_BUTTON_ACTIVE_BG), ("!active", DARK_HEADER_BG)], 
    relief=[("active", "sunken"), ("!active", "raised")]
)
style.configure("TPanedwindow", background=DARK_BACKGROUND)


window_width = 1250
window_height = 850
root.geometry(f"{window_width}x{window_height}")
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
position_top = int(screen_height / 2 - window_height / 2)
position_right = int(screen_width / 2 - window_width / 2)
root.geometry(f"{window_width}x{window_height}+{position_right}+{position_top}")

auto_scroll_var = tk.BooleanVar(value=True) 
autopost_var = tk.BooleanVar(value=False) 
post_limit_var = tk.StringVar(value="1") 
photo_count_var = tk.StringVar(value="1") 

content_frame = ttk.Frame(root, padding="10 10 10 10", style="TFrame")
content_frame.pack(fill="both", expand=True)

progress_outer_frame = ttk.Frame(content_frame) 
progress_outer_frame.pack(pady=5, fill="x")

progress_bar = ttk.Progressbar(progress_outer_frame, length=800, mode='determinate', style="Horizontal.TProgressbar")
progress_bar.pack(pady=(0, 5), fill="x", expand=True) 

progress_label = ttk.Label(progress_outer_frame, text="Осталось постов: 0")
progress_label.pack() 

info_frame = ttk.Frame(content_frame)
info_frame.pack(pady=5, fill="x")

successful_posts_label = ttk.Label(info_frame, text="Успешные публикации: 0", foreground=DARK_SUCCESS_TEXT_COLOR, font=HEADER_FONT)
successful_posts_label.pack(side=tk.LEFT, padx=(0, 20))
successful_posts_label.config(cursor="hand2")
successful_posts_label.bind("<Button-1>", lambda e: copy_tokens_to_clipboard(final_successful_tokens_for_ui_copy, "успешных"))


failed_posts_label = ttk.Label(info_frame, text="Неуспешные публикации: 0", foreground=DARK_FAILED_TEXT_COLOR, font=HEADER_FONT)
failed_posts_label.pack(side=tk.LEFT, padx=(0,20))
failed_posts_label.config(cursor="hand2")
failed_posts_label.bind("<Button-1>", lambda e: copy_tokens_to_clipboard(final_failed_tokens_for_ui_copy, "неуспешных"))


wifi_status_label_ui = ttk.Label(info_frame, textvariable=current_wifi_status_var, font=FONT_STYLE)
wifi_status_label_ui.pack(side=tk.LEFT, padx=(0,10))
ip_label_ui = ttk.Label(info_frame, textvariable=current_ip_var, font=FONT_STYLE)
ip_label_ui.pack(side=tk.LEFT, padx=(0,10))
location_label_ui = ttk.Label(info_frame, textvariable=current_location_var, font=FONT_STYLE)
location_label_ui.pack(side=tk.LEFT, padx=(0,10))


last_post_frame = ttk.Frame(content_frame)
last_post_frame.pack(pady=5, fill="x")

last_post_label = ttk.Label(last_post_frame, text="Последний выложенный пост:")
last_post_label.pack(side=tk.LEFT, padx=(0, 10))
last_post_var = tk.StringVar() 
last_post_entry = ttk.Entry(last_post_frame, textvariable=last_post_var, width=30) 
last_post_entry.pack(side=tk.LEFT)

autopost_settings_frame = ttk.Frame(content_frame)
autopost_settings_frame.pack(pady=5, fill="x")

autopost_check = ttk.Checkbutton(autopost_settings_frame, text="Автопубликация", variable=autopost_var, state=tk.DISABLED) 
autopost_check.pack(side=tk.LEFT, padx=(0, 20))

post_limit_label = ttk.Label(autopost_settings_frame, text="Лимит постов:")
post_limit_label.pack(side=tk.LEFT, padx=(0, 5))
post_limit_entry = ttk.Entry(autopost_settings_frame, textvariable=post_limit_var, width=10, state=tk.DISABLED)
post_limit_entry.pack(side=tk.LEFT, padx=(0, 20))

photo_count_label = ttk.Label(autopost_settings_frame, text="Количество фотографий:")
photo_count_label.pack(side=tk.LEFT, padx=(0, 5))
photo_count_entry = ttk.Entry(autopost_settings_frame, textvariable=photo_count_var, width=10, state=tk.DISABLED)
photo_count_entry.pack(side=tk.LEFT)

account_management_frame = ttk.Frame(content_frame)
account_management_frame.pack(pady=5, fill="x")

account_button = ttk.Button(account_management_frame, text="Указать аккаунты", command=load_tokens_ui_update)
account_button.pack(side=tk.LEFT, padx=(0, 10))
tokens_label = ttk.Label(account_management_frame, text="Токенов: 0")
tokens_label.pack(side=tk.LEFT)

root.drop_target_register(DND_FILES) 
root.dnd_bind('<<Drop>>', lambda event: None) 

controls_frame = ttk.Frame(content_frame)
controls_frame.pack(pady=10) 

post_button = ttk.Button(controls_frame, text="Выложить пост", command=submit_post, state=tk.DISABLED)
post_button.pack(side=tk.LEFT, padx=(0, 10))

stop_button = ttk.Button(controls_frame, text="Остановить", command=stop_posting, state=tk.DISABLED)
stop_button.pack(side=tk.LEFT, padx=(0, 10))

add_schedule_button = ttk.Button(controls_frame, text="Добавить набор публикаций", command=add_posting_set, state=tk.DISABLED)
add_schedule_button.pack(side=tk.LEFT, padx=(0, 10))

remove_schedule_button = ttk.Button(controls_frame, text="Удалить выбранные", command=remove_posting_set, state=tk.DISABLED)
remove_schedule_button.pack(side=tk.LEFT, padx=(0, 10))


paned_window_schedule_log = ttk.PanedWindow(content_frame, orient=tk.HORIZONTAL)
paned_window_schedule_log.pack(pady=10, fill="both", expand=True)

left_table_frame_outer = ttk.LabelFrame(paned_window_schedule_log, text="План публикаций", padding=5)
paned_window_schedule_log.add(left_table_frame_outer, weight=1) 

posting_schedule_table = ttk.Treeview(left_table_frame_outer, columns=("№", "Фото", "Видео", "Текст"), show='headings', selectmode='extended', height=8)
for col_name in ("№", "Фото", "Видео", "Текст"):
    posting_schedule_table.heading(col_name, text=col_name)
    if col_name == "Текст": 
        posting_schedule_table.column(col_name, width=250, minwidth=150, stretch=tk.YES)
    elif col_name == "№": 
        posting_schedule_table.column(col_name, width=40, minwidth=30, stretch=tk.NO, anchor=tk.CENTER)
    else: 
        posting_schedule_table.column(col_name, width=100, minwidth=70, stretch=tk.NO, anchor=tk.W)

posting_schedule_table.pack(side=tk.LEFT, fill="both", expand=True)
scrollbar_table = ttk.Scrollbar(left_table_frame_outer, orient="vertical", command=posting_schedule_table.yview)
scrollbar_table.pack(side=tk.RIGHT, fill="y")
posting_schedule_table.config(yscrollcommand=scrollbar_table.set)
posting_schedule_table.bind("<Button-3>", lambda event: show_table_context_menu(event, posting_schedule_table))
posting_schedule_table.bind("<Control-a>", lambda event: select_all_table(posting_schedule_table)) 
posting_schedule_table.bind("<Control-A>", lambda event: select_all_table(posting_schedule_table)) 
posting_schedule_table.bind("<Delete>", lambda event: remove_posting_set()) 


right_log_frame_outer = ttk.LabelFrame(paned_window_schedule_log, text="Лог процесса", padding=5)
paned_window_schedule_log.add(right_log_frame_outer, weight=1)

output_text_frame = ttk.Frame(right_log_frame_outer) 
output_text_frame.pack(pady=(0,0), fill="both", expand=True)

output_text = tk.Text(output_text_frame, width=50, height=8, font=FONT_STYLE, wrap="word",
                      bg=DARK_WIDGET_BG, fg=DARK_WIDGET_FG, insertbackground=DARK_INSERT_BG,
                      selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                      borderwidth=1, relief="sunken", state=tk.DISABLED) 
output_text.pack(side=tk.LEFT, fill="both", expand=True)

output_scrollbar = ttk.Scrollbar(output_text_frame, orient="vertical", command=output_text.yview)
output_scrollbar.pack(side=tk.RIGHT, fill="y")
output_text.config(yscrollcommand=output_scrollbar.set)
output_text.bind("<Button-3>", lambda event: show_context_menu(event, output_text))


paned_window_results = ttk.PanedWindow(content_frame, orient=tk.HORIZONTAL)
paned_window_results.pack(pady=10, fill="both", expand=True)

successful_frame = ttk.LabelFrame(paned_window_results, text="Успешные публикации", padding=5)
paned_window_results.add(successful_frame, weight=1)

successful_text_inner_frame = ttk.Frame(successful_frame)
successful_text_inner_frame.pack(fill="both", expand=True)
successful_text = tk.Text(successful_text_inner_frame, width=40, height=8, font=FONT_STYLE, wrap="word",
                          bg=DARK_ACCENT_SUCCESS_BG, fg=DARK_FOREGROUND, insertbackground=DARK_INSERT_BG,
                          selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                          borderwidth=1, relief="sunken", cursor="arrow") 
successful_text.pack(side=tk.LEFT, fill="both", expand=True)
successful_scrollbar = ttk.Scrollbar(successful_text_inner_frame, orient="vertical", command=successful_text.yview)
successful_scrollbar.pack(side=tk.RIGHT, fill="y")
successful_text.config(yscrollcommand=successful_scrollbar.set)
successful_text.bind("<Button-3>", lambda event: show_context_menu(event, successful_text))


failed_frame = ttk.LabelFrame(paned_window_results, text="Неуспешные публикации", padding=5)
paned_window_results.add(failed_frame, weight=1)

failed_text_inner_frame = ttk.Frame(failed_frame)
failed_text_inner_frame.pack(fill="both", expand=True)
failed_text = tk.Text(failed_text_inner_frame, width=40, height=8, font=FONT_STYLE, wrap="word",
                      bg=DARK_ACCENT_FAILED_BG, fg=DARK_FOREGROUND, insertbackground=DARK_INSERT_BG,
                      selectbackground=DARK_SELECT_BG, selectforeground=DARK_SELECT_FG,
                      borderwidth=1, relief="sunken", state=tk.DISABLED) 
failed_text.pack(side=tk.LEFT, fill="both", expand=True)
failed_scrollbar = ttk.Scrollbar(failed_text_inner_frame, orient="vertical", command=failed_text.yview)
failed_scrollbar.pack(side=tk.RIGHT, fill="y")
failed_text.config(yscrollcommand=failed_scrollbar.set)
failed_text.bind("<Button-3>", lambda event: show_context_menu(event, failed_text))


load_last_post()
last_post_var.trace_add('write', save_last_post) 

load_autopost_state()
autopost_var.trace_add('write', save_autopost_state) 
autopost_var.trace_add('write', update_add_schedule_button_state) 

load_post_and_photo_settings()
post_limit_var.trace_add('write', save_post_limit_settings)
photo_count_var.trace_add('write', save_photo_count_settings)

load_tokens_ui_update() 

root.after(100, process_queue)
root.after(200, update_info_display)  

root.mainloop()